Qt 事件系统

1. 相关资料

可查阅 Qt 帮助The Event SystemQEvent部分

2. 附件说明

3. 相关类

  • QAbstractEventDispatcher 事件分发器.
  • QEvent
  • QCoreApplication, QApplication
  • QEventLoop

4. 概述

  • 每个事件均为QEvent子类的一个实例, 每种事件均有一个枚举值用以识别事件类型. 可以使用QEvent::type()来返回事件类型的枚举值, 从而用于比较.
    • 可使用 QEvent::registerEventType() 返回一个可用的枚举值.

5. 事件分类

  • 根据事件发生与分发的不同, 可将事件分为三类:

    • Spontaneous 事件, 从系统中得到消息(比如系统的按键, 鼠标等) , 转换为 QEvent 后, 被 Qt 事件系统处理.
    • Posted 事件, 由 Qt 或应用程序产生, 被放入事件队列中 , 再通过事件循环处理. 使用 QCoreApplication::postEvnet() 发送事件.
    • Send 事件, 由 Qt 或应用程序产生, 使用 QCoreApplication::sendEvent() 发送的事件.
  • 示例: 窗口的重绘事件处理函数PaintEvent(), 可被以上这三种类型事件调用:

    • 窗口被覆盖或再次重新显示时, 系统产生自发事件 spontaneous 来请求重绘窗口.
    • 手动调用update()函数, 产生 Posted 事件, 并放入消息队列. 由事件循环来请求重绘窗口. 该过程会被 Qt 优化(详见 Qt paintEvent()).
    • 手动调用repaint()函数, 产生 Send 事件, 直接调用该处理函数. 该过程不会被 Qt 优化.

5.1. Spontaneous事件发送

  • 系统底层事件通过QAbstractEventDispatcher转换到 Qt 的事件循环中.

5.2. Post事件发送

  • 需要在堆上开辟内存, 用完后会被自动删除.
  • 使用 postEvent() 将事件放入事件队列, 并立即返回.
  • 事件循环 QEventLoop 在空闲时判断事件队列是否为空, 最后使用 sendEvent() 派发这些事件队列中的事件.
  • 可以手动使用: QCoreApplication::sendPostedEvents() 来派发事件队列中的所有事件(相当于清空了当前事件队列).
  • 每个线程都有一个事件队列.
  • 函数原型

    1
    2
    3
    [static] void QCoreApplication::postEvent(QObject *receiver,
    QEvent *event,
    int priority = Qt::NormalEventPriority);
  • 使用示例:

    1
    2
    QApplication::postEvent(mainWin,
    new QMouseEvent(QEvent::MouseButtonPress, pos, 0, 0, 0));

5.3. Send事件发送

  • 需要在栈上开辟内存.
  • 使用 sendEvent() 发送. 该函数会调用 notify() 进行事件分发, 并返回事件执行的返回值.
  • 本过程不涉及 事件队列, 事件循环 等. 但仍可以进行事件过滤, 可使用全部的事件处理手段.
  • 函数原型

    1
    2
    3
    [static] bool QCoreApplication::sendEvent(
    QObject *receiver,
    QEvent *event);
  • 使用示例:

    1
    2
    QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
    QApplication::sendEvent(mainWindow, &event);

6. 事件发送

  • 事件分发器 QAbstractEventDispatcher 管理着 Qt 的事件队列, 从系统或其他事件源接收事件, 再发送给 QCoreApplication 或 QApplication 实例来进行处理.

6.1. 直接派发

  • sendEvent 事件通过调用 QApplication::notify() 分发, 直接进入事件的派发和处理.

6.2. 通过事件循环方式

postEvent 和 自发事件

  • Qt 主线程事件循环
    • 使用 QCoreApplication::exec() 启动.
    • 在 QCoreApplication::exit() 后退出.
  • 本地事件循环使用 QEventLoop 构建.

6.2.1. 事件循环源码解析

  • 步骤

    1. 处理 Qt 事件队列中的事件(也就是 post 事件), 直至为空.
    2. 处理系统消息队列中的消息(也就是自发事件), 直至为空.
    3. 执行第 2 步时, 会产生新的 Qt 事件, 然后继续处理 Qt 事件队列中的事件.
  • 源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    while (!exit_was_called) {
    // 处理事件队列 中的事件
    while (!posted_event_queue_is_empty) {
    process_next_posted_event();
    }
    //(自发事件) 处理 系统消息队列 中的消息 (在处理过程中产生新的 Qt 事件)
    while (!spontaneous_event_queue_is_empty) {
    process_next_spontaneous_event();
    }
    // 对新产生的事件进行处理
    while (!posted_event_queue_is_empty) {
    process_next_posted_event();
    }
    }

7. 事件传递

  • 一个事件产生后, 会先传送给最上层的子控件, 若在这个传送过程中没有被标记为被处理, 将会继续传送给其父控件组中其他可接受该事件的控件, 直到该事件被标记为已处理, 或到最顶层窗体.
  • 事件的传播是在组件层次上面的, 而不是依靠类继承机制. eg: 若该组件不接受本事件, 则会继续传递给其父组件列表中能处理该事件的组件, 而不是父对象.

7.1. 过程

  • 一个事件产生后, 会根据事件类型 不同, 而被以不同的方式分发出去.
  • 不同方式分发出去的事件, 最终都要经过 QCoreApplication::notify() 来对事件进行处理.
  • 在 notify() 函数中, 先要将本事件传递给 receiver 处理.
    • 如果在本 QCoreApplication 安装了事件过滤器, 本程序中发送给所有对象的事件都要经过这些事件过滤器处理. 若在其中没有 return, 则继续传递.
    • 如果在 receiver 安装了事件过滤器, 发送给该 receiver 的事件要先经过这些事件过滤器处理. 若在其中没有 return, 则继续传递.
    • 传递给 receiver 的 event() 函数进行处理. 若该事件为自定义事件, 会在函数中调用 customEvent() 函数. 而其他 Qt 事件, 一般会分发给其他 event handler 函数.
    • 传递到特定 event handler 函数, 在其中对事件进行处理.
      • event->accept(), 本事件传递结束.
      • event->ignore(), 本事件继续向 receiver 的父控件传递.
  • 传递本事件给 receiver 的父控件们, 直到传递过程被终止 (return), 事件被 accept, 或 到达顶层窗体.

7.2. 终止事件传递的方法

  • QApplication::notify() 有 bool 返回值
    • 只要返回, 就会终止事件传递过程. 且如果是 sendEvent(), 会返回值.
  • QObject::eventFilter(), QObject::event() 有 bool 返回值.
    • return true : 表示事件已被处理, 不会继续传递
    • return false : 表示事件需要继续传递
  • 对于各事件处理函数 (eg: mouseReleaseEvent()), 无返回值.
    • event->accept() : 表示事件已被处理, 不会继续传递
    • event->ignore() : 表示事件需要继续传递

8. notify()

  • 不论是哪种事件, 最后都由 notify() 负责派发.
  • 在 notify 中, 负责调用各事件过滤器, event() 等. 可在本源码中明白事件传递的过程.

8.1. notify相关源码解析

  • QApplication 重写了 QCoreApplication 的 notify(). 实现了众多 event 的分发.
  • 每个 event 分发过程类似, 都会遍历 receiver 及其 父控件们. 直到中断整个事件传递过程.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
// no events are delivered after ~QCoreApplication() has started
if (QCoreApplicationPrivate::is_app_closing)
return true;
return doNotify(receiver, event);
}

static bool doNotify(QObject *receiver, QEvent *event)
{
if (receiver == 0) { // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
}
//... 省略了一些内容

return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}

// 将事件传递给事件过滤器和接受者
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
/*--- 1. 发送给所有 Application 的 eventFilter ---*/
// send to all application event filters (only does anything in the main thread)
if (QCoreApplication::self
&& receiver->d_func()->threadData->thread == mainThread()
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event))
return true;

/*--- 2. 发送给所有 receiver 的 eventFilter ---*/
if (sendThroughObjectEventFilters(receiver, event))
return true;

/*--- 3. 调用接受者的 event ---*/
return receiver->event(event);
}

bool QApplication::notify(QObject *receiver, QEvent *e)
{
//...
case QEvent::ToolTip:
case QEvent::WhatsThis:
case QEvent::QueryWhatsThis:
{
QWidget* w = static_cast<QWidget *>(receiver); // 接受者
QHelpEvent *help = static_cast<QHelpEvent*>(e);
QPoint relpos = help->pos();
bool eventAccepted = help->isAccepted();
while (w) { // 遍历接受者及其父控件
QHelpEvent he(help->type(), relpos, help->globalPos());
he.spont = e->spontaneous(); // 是否为自发事件
// 发送本事件到某控件, 并接受返回值
res = d->notify_helper(w, w == receiver ? help : &he);
e->spont = false;
// 查看事件是否 accept
eventAccepted = (w == receiver ? help : &he)->isAccepted();
// 返回 true, 且事件被 accept 或 本控件为一个独立的窗体 则不继续传递该事件
if ((res && eventAccepted) || w->isWindow())
break;

relpos += w->pos();
w = w->parentWidget();
}
help->setAccepted(eventAccepted);
}
break;
//...
}

9. event()

  • 源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
bool QObject::event(QEvent *e)
{
switch (e->type()) {
case QEvent::Timer:
timerEvent((QTimerEvent*)e);
break;
case QEvent::ChildAdded:
case QEvent::ChildPolished:
case QEvent::ChildRemoved:
childEvent((QChildEvent*)e);
break;
case QEvent::DeferredDelete:
qDeleteInEventHandler(this);
break;
case QEvent::MetaCall: {
//... 省略
break;
}
case QEvent::ThreadChange: {
//... 省略
break;
}

default:
// 自定义事件
if (e->type() >= QEvent::User) {
customEvent(e);
break;
}
return false;
}
return true;
}

— 道理越辩越明, 欢迎留言讨论. —

0%